﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Threading;

namespace SimpleIpc
{
  internal abstract class SenderReceiverBase : IDisposable
  {
    StateMachineHelper<StateMachineState> stateMachine;

    Mutex channelMutex;
    string channelName;

    MessageQueue queue;

    ManualResetEvent stopMainLoopSignal = null;
    Thread mainLoopThread = null;
    ManualResetEvent mainLoopStoppedSignal = null;
    StateMachineState mainLoopExitState;

    protected DetailedLogger logger;
    static object staticLockObject = typeof(SenderReceiverBase);

#if DEBUG
    protected int instanceId;

    static int nextThreadId = 0;
    static int nextInstanceId = 0;
#endif

    internal SenderReceiverBase(string channelName)
    {
      lock (staticLockObject)
      {
        instanceId = nextInstanceId++;
      }

      this.channelName = channelName;
      logger = new DetailedLogger(channelName + "_" + instanceId);
      logger.Log("Constructed OK");
    }

    public void Initialize()
    {
      bool triedToAcquireAdditionalResources = false;

      try
      {
        AcquireResources();
        triedToAcquireAdditionalResources = true;
        logger.Log("Calling derived class to acquire additional resources...");
        AcquireAdditionalResources();
        logger.Log("Derived class completed acquisition OK");
      }
      catch (Exception ex)
      {
        logger.Log("Hit exception during construction... cleaning up", LoggerSeverity.Error);
        ReleaseResources();
        if (triedToAcquireAdditionalResources)
        {
          logger.Log("Calling derived class to release additional resources...");
          ReleaseAdditionalResources();
          logger.Log("Derived class completed release OK");
        }

        logger.Log("Propagating exception " + ex.Message + " to caller");
        throw;
      }

      CompleteInitialization();

      logger.Log("Initialized OK");
    }

    void AcquireResources()
    {
      logger.Log("Acquiring core resources");
      channelMutex = new Mutex(false, channelName + MessageReceiver.MUTEX_SUFFIX);
      queue = MessageQueue.OpenQueue(channelName + MessageReceiver.QUEUE_SUFFIX);
      stopMainLoopSignal = new ManualResetEvent(false);
      mainLoopStoppedSignal = new ManualResetEvent(false);
      mainLoopThread = new Thread(MainLoop);
      logger.Log("Core resources acquired OK");
    }

    void ReleaseResources()
    {
      logger.Log("Releasing core resources");
      DisposeAndNull(ref channelMutex);
      DisposeAndNull(ref queue);
      DisposeAndNull(ref stopMainLoopSignal);
      DisposeAndNull(ref mainLoopStoppedSignal);
      mainLoopThread = null;
      logger.Log("Core resources released");
    }

    protected void DisposeAndNull<T>(ref T target) where T : class, IDisposable
    {
      if (target != null)
        target.Dispose();

      target = null;
    }

    protected abstract void AcquireAdditionalResources();

    protected abstract void ReleaseAdditionalResources();

    protected abstract void CompleteInitialization();

    void Dispose(bool isFinalizing)
    {
      logger.Log("isFinalizing = " + isFinalizing + ", isDisposed = " + isDisposed);
      if (isFinalizing)
        logger.Log("Exceptions are expected if some items are already disposed...");

      Stop(isFinalizing);

      DateTime lockStart = DateTime.Now;
      logger.Log("About to lock(this)");
      lock (this)
      {
        if (isDisposed)
        {
          logger.Log("Already disposed; returning immediately");
          return;
        }

        isDisposed = true;
      }
      logger.Log("Released lock after " + (DateTime.Now - lockStart).TotalSeconds + " seconds");

      ReleaseResources();
      ReleaseAdditionalResources();
    }

    ~SenderReceiverBase()
    {
      Dispose(true);
    }

    public virtual void Dispose()
    {
      Dispose(false);
      GC.SuppressFinalize(this);
    }

    public void StopAsync()
    {
      ThreadPool.QueueUserWorkItem(delegate { Stop(); });
    }

    public void Stop()
    {
      Stop(false);
    }

    void Stop(bool isFinalizing)
    {
      logger.Log("Stopping; isStopped = " + isStopped + ", isFinalizing = " + isFinalizing);

      if (TimedLockThis(this, logger, delegate
      {
        if (isStopped)
        {
          logger.Log("Already stopped; returning immediately");
          return true;
        }

        logger.Log("Setting isStopped flag to true");
        isStopped = true;
        return false;
      }))
      {
        return;
      }

      //DateTime lockStart = DateTime.Now;
      //logger.Log("About to lock(this)");
      //lock (this)
      //{
      //  if (isStopped)
      //  {
      //    logger.Log("Already stopped; returning immediately");
      //    return;
      //  }

      //  logger.Log("Setting isStopped flag to true");
      //  isStopped = true;
      //}
      //logger.Log("Released lock after " + (DateTime.Now - lockStart).TotalSeconds + " seconds");

      logger.Log("Setting the event to signal the main loop to stop");
      try
      {
        stopMainLoopSignal.Set();
      }
      catch
      {
        logger.Log("Exception trying to set the stop sending event; might already disposed",
          isFinalizing ? LoggerSeverity.Information : LoggerSeverity.Error);
      }
      if (mainLoopThread != null && mainLoopThread.IsAlive)
      {
        logger.Log("Waiting for main loop to terminate...");
        mainLoopThread.Join();
        logger.Log("Main loop has terminated!");
      }

      logger.Log("Setting the stopped event so everyone knows we've stopped");
      try
      {
        mainLoopStoppedSignal.Set();
      }
      catch (ObjectDisposedException)
      {
        logger.Log("Exception trying to set the 'I have stopped' event; might already disposed",
          isFinalizing ? LoggerSeverity.Information : LoggerSeverity.Error);
      }
    }

    public static T TimedLockThis<T>(object lockObject, DetailedLogger logger, Func<T> func)
    {
      return TimedLock(lockObject, "About to lock(this)", logger, TimeSpan.MaxValue, false, func);
    }

    public static T TimedLock<T>(Type lockObject, DetailedLogger logger, Func<T> func)
    {
      return TimedLock(lockObject, "About to lock(typeof(" + lockObject.Name + "))", logger, TimeSpan.MaxValue, false, func);
    }

    public static T TimedLock<T>(object lockObject, string startMessage, DetailedLogger logger, TimeSpan maxLockTime, bool throwIfLockedTooLong, Func<T> func)
    {
      T result = default(T);
      DateTime lockStart = DateTime.Now;
      logger.Log(startMessage, LoggerSeverity.Trace);
      lock (lockObject)
      {
        result = func();
      }
      TimeSpan delta = DateTime.Now - lockStart;
      logger.Log("Released lock after " + delta.TotalSeconds + " seconds", LoggerSeverity.Trace);

      if (delta >= maxLockTime)
      {
        logger.Log("Lock time exceeded maxiumum specified time of " + maxLockTime, LoggerSeverity.Warning);
        if (throwIfLockedTooLong)
          throw new InvalidOperationException("lock took too long");
      }

      return result;
    }
  }
}
